package actor

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"sync/atomic"
	"time"

	"gitee.com/nggs/protoactor-go/actor"
)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type HttpRequest struct {
	Pattern string
	W       http.ResponseWriter
	R       *http.Request
}

type HttpResponse struct {
	W http.ResponseWriter
	R *http.Request
}

func RequestHttpResponse(pid *actor.PID, send *HttpRequest) (*HttpResponse, error) {
	return RequestHttpResponseT(pid, send, 15*time.Second)
}

func RequestHttpResponseT(pid *actor.PID, send *HttpRequest, timeout time.Duration) (*HttpResponse, error) {
	if pid == nil {
		return nil, fmt.Errorf("pid is nil")
	}
	f := RootContext.RequestFuture(pid, send, timeout)
	iRecv, err := f.Result()
	if err != nil {
		return nil, err
	}
	recv, ok := iRecv.(*HttpResponse)
	if !ok {
		return nil, fmt.Errorf("recv %#v is not HttpResponse", recv)
	}
	return recv, nil
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type HttpServer struct {
	listener     net.Listener
	serverMux    *http.ServeMux
	server       *http.Server
	handlerFuncs map[string]http.HandlerFunc
}

func NewHttpServer() *HttpServer {
	s := &HttpServer{
		serverMux:    http.NewServeMux(),
		handlerFuncs: map[string]http.HandlerFunc{},
	}
	s.server = &http.Server{
		Handler: s.serverMux,
	}
	return s
}

func (s *HttpServer) Listener() net.Listener {
	return s.listener
}

func (s *HttpServer) Listen(addr string) (err error) {
	s.listener, err = net.Listen("tcp", addr)
	if err != nil {
		return
	}
	return
}

func (s *HttpServer) Serve() {
	err := s.server.Serve(s.listener)
	if err != nil {
		log.Printf("start http server fail, %s", err)
		return
	}
}

func (s *HttpServer) ServeTLS(certFile string, keyFile string) {
	err := s.server.ServeTLS(s.listener, certFile, keyFile)
	if err != nil {
		log.Printf("start https server fail, %s", err)
		return
	}
}

func (s *HttpServer) Register(super ISuper, pattern string, fn http.HandlerFunc) {
	s.handlerFuncs[pattern] = fn
	s.serverMux.Handle(pattern, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		_, err := RequestHttpResponse(super.PID(), &HttpRequest{
			Pattern: pattern,
			W:       writer,
			R:       request,
		})
		if err != nil {
			super.Error("%s RequestHttpResponse fail, %s", pattern, err)
			return
		}
	}))
}

func (s *HttpServer) RegisterNoQueuedHandler(super ISuper, pattern string, fn http.HandlerFunc) {
	s.serverMux.Handle(pattern, fn)
}

func (s *HttpServer) Dispatch(super ISuper, sender *actor.PID, ctx actor.Context, recv *HttpRequest) {
	handlerFunc, ok := s.handlerFuncs[recv.Pattern]
	if !ok {
		super.Error("recv unsupported http request [%s]", recv.Pattern)
		return
	}
	send := &HttpResponse{
		W: recv.W,
		R: recv.R,
	}
	defer func() {
		if sender != nil {
			ctx.Send(sender, send)
		}
	}()
	handlerFunc(recv.W, recv.R)
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type HttpClientTaskID = uint64

//var gHttpClientTaskIDGenerator HttpClientTaskID
//
//func generateHttpClientTaskID() HttpClientTaskID {
//	return atomic.AddInt64(&gHttpClientTaskIDGenerator, 1)
//}

type HttpClientTaskComplete struct {
	ID HttpClientTaskID
}

type HttpClientTaskPrepare func() (request *http.Request, err error)

type HttpClientTaskCallback func(id HttpClientTaskID, request *http.Request, response *http.Response, client *http.Client, err error)

type httpClientTask struct {
	id       HttpClientTaskID
	request  *http.Request
	response *http.Response
	client   *http.Client
	callback HttpClientTaskCallback
	err      error
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type HttpClientTaskManager struct {
	idGenerator HttpClientTaskID
	tasks       map[HttpClientTaskID]*httpClientTask
	pid         *actor.PID
}

func NewHttpClientTaskManager(pid *actor.PID) *HttpClientTaskManager {
	return &HttpClientTaskManager{
		idGenerator: 0,
		tasks:       map[HttpClientTaskID]*httpClientTask{},
		pid:         pid,
	}
}

func (m *HttpClientTaskManager) nextID() HttpClientTaskID {
	return atomic.AddUint64(&m.idGenerator, 1)
}

func (m *HttpClientTaskManager) NewTask(timeout time.Duration, prepare HttpClientTaskPrepare, callback HttpClientTaskCallback) (taskID HttpClientTaskID, err error) {
	if timeout <= 0 {
		err = fmt.Errorf("timeout must > 0")
		return
	}

	request, e := prepare()
	if e != nil {
		err = fmt.Errorf("prepare fail, %w", e)
		return
	}
	if request == nil {
		err = fmt.Errorf("request is nil")
		return
	}

	if callback == nil {
		err = fmt.Errorf("call back is nil")
		return
	}

	client := &http.Client{Timeout: timeout}

	taskID = m.nextID()

	task := &httpClientTask{
		id:       taskID,
		request:  request,
		client:   client,
		callback: callback,
	}

	m.tasks[task.id] = task

	go func() {
		task.response, task.err = task.client.Do(task.request)
		RootContext.Send(m.pid, &HttpClientTaskComplete{ID: task.id})
	}()

	return
}

func (m *HttpClientTaskManager) OnComplete(id HttpClientTaskID) (err error) {
	task, ok := m.tasks[id]
	if !ok {
		err = fmt.Errorf("task not found, id=%d", id)
		return
	}

	task.callback(task.id, task.request, task.response, task.client, task.err)

	if task.err == nil {
		task.response.Body.Close()
	}

	delete(m.tasks, id)

	return
}
